package authorization

import (
	"errors"
	"fmt"
	"github.com/jitsucom/jitsu/server/logging"
	"github.com/jitsucom/jitsu/server/uuid"
	"github.com/spf13/viper"
	"sort"
	"sync"
	"time"
)

const (
	serviceName = "authorization"

	viperApiKeysKey                 = "api_keys"
	deprecatedViperServerApiKeysKey = "server.api_keys"
	deprecatedViperAuthKey          = "server.auth"
	deprecatedViperS2SKey           = "server.s2s_auth"

	defaultTokenID = "defaultid"
)

type Service struct {
	sync.RWMutex

	tokensHolder *TokensHolder
	//will call after every reloading
	DestinationsForceReload func()
}

func NewService(configuratorURL, configuratorToken string) (*Service, error) {
	service := &Service{
		tokensHolder: &TokensHolder{
			clientTokensOrigins: map[string][]string{},
			serverTokensOrigins: map[string][]string{},
			all:                 map[string]Token{},
			ids:                 []string{},
		},
	}

	reloadSec := viper.GetInt("server.api_keys_reload_sec")
	if reloadSec == 0 {
		//backward compatibility
		reloadSec = viper.GetInt("server.auth_reload_sec")
	}

	if reloadSec == 0 {
		return nil, errors.New("server.api_keys_reload_sec can't be empty")
	}

	reloadEvery := time.Duration(reloadSec) * time.Second

	//if api_keys is used => strict tokens
	if viper.IsSet(viperApiKeysKey) || viper.IsSet(deprecatedViperServerApiKeysKey) {
		viper.SetDefault("server.strict_auth_tokens", true)
	}

	viperKey := viperApiKeysKey
	if viper.IsSet(deprecatedViperServerApiKeysKey) {
		viperKey = deprecatedViperServerApiKeysKey
	}

	if !viper.IsSet(viperKey) && viper.IsSet(deprecatedViperAuthKey) {
		viperKey = deprecatedViperAuthKey
	}

	//returns initialized true if already service.tokenHolders is filled
	initialized, tokens, err := extractFromViper(viperKey, reloadEvery, service)
	if err != nil {
		return nil, err
	}

	if !initialized {
		//initialize via configurator URL
		if len(tokens) == 0 && configuratorURL != "" {
			if _, _, err = extractFromString(buildAuthURL(configuratorURL, configuratorToken), service, reloadEvery); err != nil {
				return nil, err
			}
		} else {
			//add deprecated viper s2s key and fill service.tokenHolders
			deprecatedS2SAuth := viper.GetStringSlice(deprecatedViperS2SKey)
			for _, s2sauth := range deprecatedS2SAuth {
				tokens = append(tokens, Token{ServerSecret: s2sauth})
			}

			service.tokensHolder = reformat(tokens)
		}
	}

	if service.tokensHolder.IsEmpty() && !viper.GetBool("server.strict_auth_tokens") {
		//autogenerated
		generatedTokenSecret := uuid.New()
		generatedToken := Token{
			ID:           defaultTokenID,
			ClientSecret: generatedTokenSecret,
			ServerSecret: generatedTokenSecret,
			Origins:      []string{},
		}

		service.tokensHolder = reformat([]Token{generatedToken})
		logging.Info("Empty authorization 'api_keys' config. Auto generate API Key:", generatedTokenSecret)
	}

	return service, nil
}

// GetClientOrigins return origins by client_secret
func (s *Service) GetClientOrigins(clientSecret string) ([]string, bool) {
	s.RLock()
	defer s.RUnlock()
	if s.tokensHolder != nil {
		origins, ok := s.tokensHolder.clientTokensOrigins[clientSecret]
		return origins, ok
	} else {
		logging.Errorf("Authorization service is not initialized")
		return nil, false
	}
}

// GetServerOrigins return origins by server_secret
func (s *Service) GetServerOrigins(serverSecret string) ([]string, bool) {
	s.RLock()
	defer s.RUnlock()

	origins, ok := s.tokensHolder.serverTokensOrigins[serverSecret]
	return origins, ok
}

// GetAllTokenIDs return all token ids
func (s *Service) GetAllTokenIDs() []string {
	s.RLock()
	defer s.RUnlock()

	ids := make([]string, 0, len(s.tokensHolder.ids))
	for _, id := range s.tokensHolder.ids {
		ids = append(ids, id)
	}
	sort.Strings(ids)
	return ids
}

// GetAllIDsByToken return token ids by token identity(client_secret/server_secret/token id)
func (s *Service) GetAllIDsByToken(tokenIDentity []string) (ids []string) {
	s.RLock()
	defer s.RUnlock()

	deduplication := map[string]bool{}
	for _, tokenFilter := range tokenIDentity {
		if tokenFilter == "" {
			continue
		}

		tokenObj, ok := s.tokensHolder.all[tokenFilter]
		if ok {
			deduplication[tokenObj.ID] = true
		}
	}

	for id := range deduplication {
		ids = append(ids, id)
	}
	sort.Strings(ids)
	return
}

// GetTokenID return token id by client_secret/server_secret/token id
// return "" if token wasn't found
func (s *Service) GetTokenID(tokenFilter string) string {
	s.RLock()
	defer s.RUnlock()

	token, ok := s.tokensHolder.all[tokenFilter]
	if ok {
		return token.ID
	}
	return ""
}

// GetToken return token object by client_secret/server_secret/token id
func (s *Service) GetToken(tokenFilter string) *Token {
	s.RLock()
	defer s.RUnlock()

	token, ok := s.tokensHolder.all[tokenFilter]
	if ok {
		return &token
	}
	return nil
}

// parse and set tokensHolder with lock
func (s *Service) updateTokens(payload []byte) {
	tokens, err := parseFromBytes(payload)
	if err != nil {
		logging.Errorf("Error updating authorization tokens: %v", err)
	} else {
		s.Lock()
		s.tokensHolder = reformat(tokens)
		s.Unlock()

		//we should reload destinations after all changes in authorization service
		if s.DestinationsForceReload != nil {
			s.DestinationsForceReload()
		}
	}
}

func buildAuthURL(configuratorURL, configuratorToken string) string {
	return fmt.Sprintf("%s/api/v1/apikeys?token=%s", configuratorURL, configuratorToken)
}
